# UpdateListener.java

    package org.codefilarete.stalactite.engine.listener;

    import java.util.ArrayList;
    import java.util.List;
    import java.util.Map;

    import org.codefilarete.tool.Duo;
    import org.codefilarete.stalactite.mapping.Mapping;
    import org.codefilarete.stalactite.mapping.Mapping.UpwhereColumn;
    import org.codefilarete.stalactite.sql.ddl.structure.Table;

    /**
     * @author Guillaume Mary
     */
    public interface UpdateListener<C> {

    	default void beforeUpdate(Iterable<? extends Duo<C, C>> payloads, boolean allColumnsStatement) {

    	}

    	default void afterUpdate(Iterable<? extends Duo<C, C>> entities, boolean allColumnsStatement) {

    	}

    	default void onUpdateError(Iterable<? extends C> entities, RuntimeException runtimeException) {

    	}

    	/**
    	 * Payload for listener's methods. Carries modified + unmodified entities and the columns + values
    	 * which must be updated.
    	 * Not expected to be used elsewhere than the {@link UpdateListener} mechanism.
    	 *
    	 * @param <C> entities type
    	 * @param <T> target table type
    	 */
    	class UpdatePayload<C, T extends Table<T>> {
    		private final Duo<? extends C, ? extends C> entities;

    		private final Map<UpwhereColumn<T>, ?> values;

    		public UpdatePayload(Duo<? extends C, ? extends C> entities, Map<UpwhereColumn<T>, ?> values) {
    			this.entities = entities;
    			this.values = values;
    		}

    		/**
    		 * Gives the tuple of modified (left) and unmodified (right) instances
    		 *
    		 * @return constructor argument
    		 */
    		public Duo<C, C> getEntities() {
    			return (Duo<C, C>) entities;
    		}

    		/**
    		 * Gives the map of columns and values which must be updated in database
    		 *
    		 * @return constructor argument
    		 */
    		public Map<UpwhereColumn<T>, ?> getValues() {
    			return values;
    		}
    	}

    	/**
    	 * Default behavior for giving {@link UpdatePayload} from some entities and a {@link Mapping}.
    	 *
    	 * @param entities modified + unmodified entities
    	 * @param allColumns indicates if all columns must be updated or not
    	 * @param mapping the strategy that will compute updatable columns and values from modified + unmodified entities
    	 * @param <C> entities type
    	 * @param <T> target table type
    	 * @return arguments wrapped into an {@link UpdatePayload}, enhanced with updatable columns and values
    	 */
    	static <C, T extends Table<T>> Iterable<UpdatePayload<C, T>> computePayloads(Iterable<? extends Duo<C, C>> entities,
    																				 boolean allColumns,
    																				 Mapping<C, T> mapping) {
    		return computePayloads(entities, allColumns, mapping::getUpdateValues);
    	}

    	static <C, T extends Table<T>> Iterable<UpdatePayload<C, T>> computePayloads(Iterable<? extends Duo<C, C>> entities,
    																				 boolean allColumns,
    																				 UpdateValuesProvider<C, T> mappingStrategy) {
    		List<UpdatePayload<C, T>> result = new ArrayList<>();
    		for (Duo<? extends C, ? extends C> next : entities) {
    			C modified = next.getLeft();
    			C unmodified = next.getRight();
    			// finding differences between modified instances and unmodified ones
    			Map<UpwhereColumn<T>, ?> updateValues = mappingStrategy.getUpdateValues(modified, unmodified, allColumns);
    			UpdatePayload<C, T> payload = new UpdatePayload<>(next, updateValues);
    			result.add(payload);
    		}
    		return result;
    	}

    	interface UpdateValuesProvider<C, T extends Table<T>> {
    		Map<UpwhereColumn<T>, ?> getUpdateValues(C modified, C unmodified, boolean allColumns);
    	}

    }